import os
# https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml
# https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml
# pip install PyUserInput
import pykeyboard
import pymouse
import time
from time import sleep

from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains as hover, ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from PIL import ImageGrab
from pykeyboard import ReplyKeyboard


class OperationElement(object):

    def __init__(self, driver, timeout=5):
        """
        初始化类参数
        :param driver: 浏览器session
        :param timeout: 等待超时默认20秒
        """
        self.mouse = pymouse.PyMouse()
        self.key_bord = ReplyKeyboard()
        self.driver = driver
        self.support = WebDriverWait(driver=self.driver, timeout=timeout)

    def F5(self):
        """浏览器刷新"""
        self.driver.refresh()

    def get(self, url):
        """请求url的参数"""
        self.driver.get(url)

    def implicit(self, tm):
        """
        隐试等待
        :param tm: 时间
        :return:
        """
        return self.driver.implicitly_wait(tm)

    def drag(self, source, target):
        """
        元素拖拽
        :param source: 拖拽元素对象初始位置
        :param target: 拖拽元素位置到达位置
        """
        hover(self.driver).drag_and_drop(self.operation_element(source), self.operation_element(target)).perform()

    def driver_quit(self):
        """
        浏览器退出
        :return:
        """
        self.driver.quit()

    def max_window(self):
        """
        最大窗口
        :return:
        """
        self.driver.maximize_window()

    def search_element(self, element):
        """
        操作类元素
        :param element: 如：（By.XPATH, "//*[contains(text(),'请选择要登录的公司')]"）
        :return: 对应的元素
        """
        by = element[0]
        element_value = element[1]
        if by == "xpath":
            return self.driver.find_element(By.XPATH, element_value)
        elif by == "css selector":
            return self.driver.find_element(By.CSS_SELECTOR, element_value)
        elif by == "name":
            return self.driver.find_element(By.NAME, element_value)
        elif by == "class":
            return self.driver.find_element(By.CLASS_NAME, element_value)
        elif by == "text":
            return self.driver.find_element(By.LINK_TEXT, element_value)
        elif by == "id":
            return self.driver.find_element(By.ID, element_value)
        else:
            raise NoSuchElementException(f'抱歉,未找到你所定义的元素【{element}】')

    def search_elements(self, element):
        """
        操作类元素
        :param element: 如：（By.XPATH, "//*[contains(text(),'请选择要登录的公司')]"）
        :return: 对应的元素
        """
        by = element[0]
        element_value = element[1]
        if by == "xpath":
            return self.driver.find_elements(By.XPATH, element_value)
        elif by == "css selector":
            return self.driver.find_elements(By.CSS_SELECTOR, element_value)
        elif by == "name":
            return self.driver.find_elements(By.NAME, element_value)
        elif by == "class":
            return self.driver.find_elements(By.CLASS_NAME, element_value)
        elif by == "text":
            return self.driver.find_elements(By.LINK_TEXT, element_value)
        elif by == "id":
            return self.driver.find_elements(By.ID, element_value)
        else:
            raise NoSuchElementException(f'抱歉,未找到你所定义的元素【{element}】')


    def screen_base64_shot(self):
        """
        截图
        :return: base64
        """
        return self.driver.get_screenshot_as_base64()

    def execute_js(self, js):
        """
        执行js
        :param js: js语句
        :return:
        """
        return self.driver.execute_script(js)

    def current_windows(self):
        """
        当前窗口句柄
        :return: 返回当前窗口句柄ID
        """
        return self.driver.current_window_handle

    def hovers(self, element):
        """
        悬浮元素
        :param element: self.hovers(((By.XPATH, "(//tr[starts-with(@class, 'ivu-table-row')])[1]")))
        :return: 返回对应的操作元素
        """
        element_hover = self.operation_element(element)
        return hover(self.driver).move_to_element(element_hover).perform()

    def more_windows(self):
        """
        全部窗口句柄
        :return: 返回全部窗口句柄ID
        """
        return self.driver.window_handles

    def switch_windows(self, name):
        """
        切换窗口
        :param name: 切换到窗口列表名字，如[1]
        :return: 对应的窗口
        """
        windows = self.more_windows()
        return self.driver.switch_to.window(windows[name])

    def switch_frame(self, frame_reference):
        """
        切换frame位置
        :param frame_reference: 框架位置
        :return: 返回对应框架中
        """
        return self.driver.switch_to.frame(frame_reference)

    def switch_frame_out(self):
        """
        返回到顶层iframe之上
        :return:
        """
        return self.driver.switch_to.default_content()

    def dub_click(self,element):
        """
        双击
        :param element:
        :return:
        """
        ActionChains(self.driver).double_click(element).perform()

    def release_frame(self):
        """释放frame"""
        return self.driver.switch_to.default_content()

    def close_current_windows(self):
        """
        关闭当前窗口
        :return:
        """
        self.driver.close()
    
    def operation_element(self, element):
        """
        显示等待某一个元素是否存在，默认超时20秒，每次0.5秒侦查一次是否存在
        :param element: 如：如：operation_element(By.XPATH, "//*[contains(text(),'请选择要登录的公司')]")).click()
        :param timeout: 如：20
        :param wait_time: 默认等待2秒
        :return: 存在则返回，不存在则抛出异常！
        """
        return self.support.until(EC.presence_of_element_located(element),message=f'元素:【{element}】超时...')

    def click(self, element, *args):
        """
        判断元素是否可点击,当第一次点击报错，等待默认时间2秒后再执行点击操作是否可点击，如过还是不可点击，就抛出异常错误
        :param element: self.is_click((By.XPATH, "(//button[starts-with(@class, 'ivu-btn')])[5]"))
        :param wait_time: 等待时间
        :return:
        """
        self.xpath(element, *args).click()

    def send_keys(self, element, *args, value):
        """
        判断是否可执行输入，当第一次出现异常，默认等待2秒后再次尝试是否可输入，直到再次获得信息
        :param element: self.is_send(By.XPATH, "(//button[starts-with(@class, 'ivu-btn')])[5]"), "小明")
        :param value: "小明"
        :param wait_time: 等待时间
        :return: ...
        """
        self.clear(element, *args)
        self.xpath(element, *args).send_keys(value)

    def clear(self, element, *args):
        """"清空"""
        self.xpath(element, *args).clear()

    def text(self, element, *args):
        """
        获取元素中的文本值，第一次获取文本值如果为空，就默认等待2秒时间，再次获取
        :param element: self.is_text((By.XPATH, "(//button[starts-with(@class, 'ivu-btn')])[5]"))
        :param wait_time: 等待时间
        :return: 返回对应的文本值
        """
        return self.xpath(element, *args).text


    def switch_alert(self):  # 警告窗口处理
        """
        text 返回alert/confirm/prompt 中的文字信息。
             accept 点击确认按钮。
             dismiss 点击取消按钮，如果有的话。
             send_keys 输入值，这个alert\confirm 没有对话框就不能用了，不然会报错。
        """
        return self.driver.switch_to_alert()

    def current_url(self):
        """获取当前网页上的url地址是什么"""
        return self.driver.current_url

    def is_attribute_class(self, element, text):
        """
        获取元素列表中的属性值(该项为class)
        :param element: is_attribute((By.XPATH, "//*[contains(text(),'请选择要登录的公司')]"))
        :param text: 属性内容是否包含，包含返回True, 反之返回False
        :param attribute: class
        :return: 返回对应bool，存在返回True，反之False
        """
        value = self.operation_element(element).get_attribute("class")
        return text in value

    def get_attribute_class(self, element):
        """
        获取元素列表中的属性值(该项为class)
        :param element: 元素
        :return: 返回对应的class属性值
        """
        return self.operation_element(element).get_attribute('class')

    def get_attribute_value(self, element):
        """
        获取value值属性
        :param element: 元素
        :return: 返回对应的value值
        """
        return self.operation_element(element).get_attribute('value')

    def is_element(self, element):
        """
        检查元素是否存在
        :param element: is_element((By.XPATH, "//*[contains(text(),'请选择要登录的公司')]"))
        :return: 存在返回True，不存在返回False
        """
        try:
            self.support.until(EC.presence_of_element_located(element))
            return True
        except Exception:
            return False

    def is_elements(self, element):
        try:
            self.support.until(EC.presence_of_all_elements_located(element))
            return True
        except Exception:
            return False

    def get_full_size(self):
        """
        获取当前屏幕尺寸
        :return: 返回对应尺寸
        """
        return self.driver.get_window_size()

    def is_in_text(self, element, content: str):
        """
        断定element的文本值，是否与content的文本值包含，包含返回True， 反之返回False
        :param element: is_text(By.XPATH, "//*[contains(text(),'请选择要登录的公司')]")， "小明")
        :param content:  "小明"
        :return: 相同返回True， 不相同返回False
        """
        return self.support.until(EC.text_to_be_present_in_element(element, content))

    def is_url_equal(self, url: str):
        """
        断定current_url值，是否与url相对等
        :param url: "http://www.sina.com.cn"
        :return: 相等返回True,不相等返回False
        """
        try:
            is_url = self.support.until(EC.url_to_be(url))
        except Exception:
            return False
        else:
            return is_url

    def is_url_contain(self, url: str):
        """
        断定current_url值，是否包含url值
        :param url: "http://www.sina"
        :return: 包含返回True,不包含返回False
        """
        try:
            is_url = self.support.until(EC.url_contains(url))
        except Exception:
            return False
        else:
            return is_url

    def is_attribute_value(self, element, text: str):
        """
        断定当前element下value属性值，是否包含text
        :param element: is_attribute(By.XPATH, "//*[contains(text(),'请选择要登录的公司')]")， "小明")
        :param text: "true"
        :return: 包含返回True,不相等返回False
        """
        return self.support.until(EC.text_to_be_present_in_element_value(element, text))

    def is_displayed(self, element):
        """
        检查元素是否可以在web界面上看见
        :param element: 如，self.is_element_exist((By.XPATH, "//*[contains(text(),'请选择要登录的公司')]"))
        :return: 可见返回True，反之返回False
        """
        return self.support.until(EC.visibility_of_element_located(element))

    def is_notdisplayed(self, element):
        """
        检查元素是否可以在web界面上看见
        :param element: 如，self.is_element_exist((By.XPATH, "//*[contains(text(),'请选择要登录的公司')]"))
        :return: 可见返回True，反之返回False
        """
        return self.support.until_not(EC.visibility_of_element_located(element))

    def xpath(self, element, *args):
        """
        将元素定义变量中包含$进行参数化转化传递
        :param element: 如，(By.XPATH, "//li[contains(text(),'$')]")
        :param args：将$变更为value
        :return:
        """
        if "$" in element[1]:
            now_value = element[1].replace("$", "{}")
        elif "{}" in element[1]:
            now_value = element[1].format(*args)
        elif "%s" in element[1]:
            now_value = element[1]%(args)
        else:
            now_value = element[1]

        return self.operation_element((element[0], now_value))

    def elements(self, elements):
        """
        查找一组元素
        :param elements:
        :return:
        """
        return self.support.until(EC.presence_of_all_elements_located(elements),message=f'元素:【{elements}】超时...')

    def get_attributed(self, element, name):
        """
        获取元素属性名称
        :param element:
        :param name:
        :return:
        """
        attribute = self.operation_element(element)
        return attribute.get_attribute(name)

    def upload(self, path, file=False):

        '''
        上传文件
        :param path: 文件目录路径
        :param file: 文件路径
        :param wait: 等待时间
        :return:
        '''
        self.key_bord.tap_key(self.key_bord.shift_key)
        self.key_bord.type_string(path)
        self.key_bord.tap_key(self.key_bord.enter_key)
        if file:
            self.key_bord.type_string(file)
            self.key_bord.tap_key(self.key_bord.enter_key)
        else:
            raise FileExistsError("{}文件不存在".format(path))

    def drag_element(self, element, x, y):
        '''
        拖拽元素函数
        :param elment: 元素位置
        :param x: 横坐标
        :param y:中坐标
        :return:
        '''
        elment=self.operation_element(element)
        action_chains = ActionChains(self.driver)
        action_chains.drag_and_drop_by_offset(elment, xoffset=x, yoffset=y).perform()

    def drag_and_drop(self, start_element, end_element,params:str):
        '''
        拖拽元素函数
        :param elment:
        :param x:
        :param y:
        :return:
        '''
        params_list = params.split('||')
        start = self.xpath(start_element, *eval(params_list[0]))
        end = self.xpath(end_element, *eval(params_list[1]))
        actions = ActionChains(self.driver)
        actions.drag_and_drop(start, end).perform()


